Expand description
Flax is a performant and easy to use Entity Component System.
The world is organized by simple identifiers known as an Entity, which can have any number of components attached to them.
Systems operate on the world’s entities and provide the application logic.
Consider reading the User Guide
Features
- Declarative component macro
- Queries
- Change detection
- Query filtering
- System execution
- Multithreaded system execution through
- Many to many entity relation and graphs
- Reflection through component metadata
- Ergonomic entity builder
- Serialization and deserialization
- (async) event subscription
- Runtime components
Live Demo
See a live demo of asteroids using wasm here.
Example Usage
// Declare static components
use flax::*;
component! {
health: f32,
regen: f32,
pos: (f32, f32),
player: (),
items: Vec<String>,
}
let mut world = World::new();
// Spawn an entity
let p = EntityBuilder::new()
.set(health(), 50.0)
.tag(player())
.set(pos(), (0.0, 0.0))
.set(regen(), 1.0)
.set_default(items())
.spawn(&mut world);
let mut query = Query::new((health().as_mut(), regen()));
// Apply health regen for all match entites
for (health, regen) in &mut query.borrow(&world) {
*health = (*health + regen).min(100.0);
}
Systems
Queries with logic can be abstracted into a system, and multiple systems can be collected into a schedule.
let regen_system = System::builder()
.with(Query::new((health().as_mut(), regen())))
.for_each(|(health, regen)| {
*health = (*health + regen).min(100.0);
})
.boxed();
let despawn_system = System::builder()
.with(Query::new(entity_ids()).filter(health().le(0.0)))
.write::<CommandBuffer>()
.build(|mut q: QueryBorrow<EntityIds, _>, cmd: &mut CommandBuffer| {
for id in &mut q {
cmd.despawn(id);
}
})
.boxed();
let mut schedule = Schedule::from([regen_system, despawn_system]);
schedule.execute_par(&mut world)?;
Relations
Flax provides first class many-many relations between entities, which is useful for tree scene hierarchies, graphs, and physics joints between entities.
Relations can be both state-less or have associated data, like spring or joint strengths.
Relations are cache friendly and querying children of does not require random access. In addition, relations are cleaned up on despawns and are stable during serialization, even if the entity ids migrate due to collisions.
See the guide for more details.
component! {
child_of(parent): () => [ Debuggable ],
}
let mut world = World::new();
let parent = Entity::builder()
.set(name(), "Parent".into())
.spawn(&mut world);
let child1 = Entity::builder()
.set(name(), "Child1".into())
.set_default(child_of(parent))
.spawn(&mut world);
Comparison to other ECS
Compared to other ecs implementations, a component is simply another Entity
identifier to which data is attached. This means the same “type” can be added to
an entity multiple times.
A limitation of existing implementations such as specs, planck, or hecs is that newtype wrappers need to be created to allow components of the same inner type to coexist.
This leads to having to forward all trait implementations trough e.g
derive-more
or dereferencing the newtypes during usage.
By making components separate from the type the components can work together without deref or newtype construction.
component! {
velocity: Vec3,
position: Vec3,
}
let vel = world.get(entity, velocity())?;
let mut pos = world.get_mut(entity, position())?;
let dt = 0.1;
*pos += *vel * dt;
On a further note, since the components have to be declared beforehand (not
always true, more on that later), it limits the amount of types which can be
inserted as components. This fixes subtle bugs which come by having the type
dictate the component, such as inserting an Arc<Type>
instead of just Type
,
which leads to subsequent systems not finding the Type
on the entity.
Having statically declared componenents makes the rust type system disallow these cases and catches these bugs earlier.
Motivation
During development of a game in school I used the hecs
ECS. It is an awesome
library, and the author Ralith has been wonderful in accepting
contributions and inquiries.
Despite this, I often made subtle bugs with similar types. The game engine was
cluttered with gigantic newtypes for Velocity
, Position
with many deref
coercions in order to coexist.
Unsafe
This library makes use of unsafe for type erasure and the allocation in storage of ComponentBuffers and Archetypes.
Re-exports
pub use archetype::ArchetypeId;
pub use archetype::BatchSpawn;
pub use commands::CommandBuffer;
pub use entity::entity_ids;
pub use entity::Entity;
pub use entity::EntityBuilder;
pub use error::Error;
pub use fetch::relations_like;
pub use fetch::EntityIds;
pub use fetch::Fetch;
pub use fetch::FetchExt;
pub use fetch::FetchItem;
pub use fetch::Mutable;
pub use fetch::Opt;
pub use fetch::OptOr;
pub use fetch::Relations;
pub use filter::All;
pub use filter::And;
pub use filter::Cmp;
pub use filter::Nothing;
pub use filter::Or;
pub use filter::With;
pub use filter::Without;
pub use components::*;
pub use query::*;
pub use schedule::*;
Modules
- Structured component storage
- Provides a buffer for holding multiple types simultaneously
- Contains a commandbuffer
- This module contains standard components that different libraries can agree on, though they don’t have to.
- Provides entity identifiers
- Defines the single error type and result alias
- Subscribe to changes in the world
- Traits for fetching multiple component values simultaneously
- Filter items yielded queries
- Query the world
- System execution
- vtable implementation for dynamic dispatching
Macros
- Declarative component generation
- Helper macro for creating a vtable for custom components
Structs
- Describes an access for a system, allowing for dependency resolution and multithreading
- Human friendly system access
- A type erased system
- Defines a strongly typed component
- Represents a type erased component along with its memory layout and drop fn.
- A unique component identifier Is not stable between executions, and should as such not be used for execution.
- Formats a component value using
Debug
- Borrow all the components of an entity at once.
- Borrow all the components of an entity at once.
- Mutually exclusive relation.
- Holds the migrated components
- Provides a name for components
- Execute a function for each item in the query in parallel batches
- Represents a relation which can connect to entities
- Allows to iterate all relations of a specific type for an entity
- See: crate::RelationIter
- Iterates reserved entity ids.
- A resource that can be shared between systems The difference between this and an
Arc<Mutex<_>>
is that this will be taken into consideration when multithreading in the schedule, and will as such not require locks. - Holds the data and an inner system satisfying all dependencies
- A system builder which allows incrementally adding data to a system function.
- Holds external context for system execution. Contains the world and a commandbuffer
- Holds the entities and components of the ECS.
- Debug formats the world with the given filter. Created using World::format_debug
Enums
- Describes a kind of access
Traits
- Allows dereferencing
AtomicRef<T>
to &T and similar “lock” types in a safe manner. - Trait alias for a ’static + Send + Sync type which can be used as a component.
- Additional data that can attach itself to a component
- Relation helper trait
- Describe an access to the world in ters of shared and unique accesses
- Describes a type which can fetch assocated Data from the system context and provide it to the system.
- A callable function
Functions
- Allows visiting and debug formatting the component
- Mutually exclusive relation.
Type Definitions
- Type alias for a function which instantiates a component
- Type alias for a function which instantiates a relation with the specified object